// OScopeCtrl.cpp : implementation file//

//#include "stdafx.h"
#include "wintypes.h"
#include "emule.h"
#include <math.h>

#include "OScopeCtrl.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__ ;
#endif

#define GetRValue(rgb) (((rgb)>>16)&0xff)
#define GetGValue(rgb) (((rgb)>>8)&0xff)
#define GetBValue(rgb) ((rgb)&0xff)

#define max(a,b) ((a)>(b))?(a):(b)

BEGIN_EVENT_TABLE(COScopeCtrl,wxControl)
  EVT_PAINT(COScopeCtrl::OnPaint)
  EVT_SIZE(COScopeCtrl::OnSize)
  EVT_TIMER(TIMER_OSCOPE,COScopeCtrl::OnTimer)
END_EVENT_TABLE()

/////////////////////////////////////////////////////////////////////////////
// COScopeCtrl
COScopeCtrl::COScopeCtrl( int NTrends,wxWindow*parent )
  : wxControl(parent,-1,wxDefaultPosition,wxDefaultSize),m_nRedrawTimer(this,TIMER_OSCOPE)
{
int i;
COLORREF PresetColor [ 16 ] = {

	RGB( 0xFF, 0x00, 0x00 ),
	RGB( 0xFF, 0xC0, 0xC0 ),

	RGB( 0xFF, 0xFF, 0x00 ),
	RGB( 0xFF, 0xA0, 0x00 ),
	RGB( 0xA0, 0x60, 0x00 ),

	RGB( 0x00, 0xFF, 0x00 ),
	RGB( 0x00, 0xA0, 0x00 ),

	RGB( 0x00, 0x00, 0xFF ),
	RGB( 0x00, 0xA0, 0xFF ),
	RGB( 0x00, 0xFF, 0xFF ),
	RGB( 0x00, 0xA0, 0xA0 ),

	RGB( 0xC0, 0xC0, 0xFF ),
	RGB( 0xFF, 0x00, 0xFF ),
	RGB( 0xA0, 0x00, 0xA0 ),

	RGB( 0xFF, 0xFF, 0xFF ),
	RGB( 0x80, 0x80, 0x80 )
};

	// since plotting is based on a LineTo for each new point
	// we need a starting point (i.e. a "previous" point)
	// use 0.0 as the default first point.
	// these are public member variables, and can be changed outside
	// (after construction).  
	// G.Hayduk: NTrends is the number of trends that will be drawn on
	// the plot. First 15 plots have predefined colors, but others will
	// be drawn with white, unless you call SetPlotColor
	m_PlotData = new PlotData_t[ NTrends ];
	m_NTrends = NTrends;

	m_dcPlot=NULL;
	m_dcGrid=NULL;
	m_bitmapPlot=NULL;
	m_bitmapGrid=NULL;

	for( i=0 ; i<m_NTrends ; i++ ){
		if( i<15 )
			m_PlotData[ i ].crPlotColor  = PresetColor[i] ;  // see also SetPlotColor
		else
			m_PlotData[ i ].crPlotColor  = RGB(255, 255, 255) ;  // see also SetPlotColor
		//m_PlotData[ i ].penPlot.CreatePen(PS_SOLID, 0, m_PlotData[ i ].crPlotColor ) ;
		m_PlotData[i].penPlot=*(wxThePenList->FindOrCreatePen(wxColour(GetRValue(m_PlotData[i].crPlotColor),GetGValue(m_PlotData[i].crPlotColor),GetBValue(m_PlotData[i].crPlotColor)),1,wxSOLID));
		m_PlotData[ i ].dPreviousPosition = 0.0 ;
		m_PlotData[ i ].nPrevY = -1;
		m_PlotData[ i ].dLowerLimit = -10.0 ;
		m_PlotData[ i ].dUpperLimit =  10.0 ;
		m_PlotData[ i ].dRange      =   m_PlotData[ i ].dUpperLimit - 
		  m_PlotData[ i ].dLowerLimit ;   // protected member variable
		m_PlotData[ i].lstPoints.AddTail(0.0);
	}

	// public variable for the number of decimal places on the y axis
	// G.Hayduk: I've deleted the possibility of changing this parameter
	// in SetRange, so change it after constructing the plot
	m_nYDecimals = 1;
	m_dcGrid = NULL;
	m_bitmapGrid = NULL;

	// set some initial values for the scaling until "SetRange" is called.
	// these are protected varaibles and must be set with SetRange
	// in order to ensure that m_dRange is updated accordingly

	oldwidth=oldheight=0;

	// m_nShiftPixels determines how much the plot shifts (in terms of pixels) 
	// with the addition of a new data point
	drawBars=false;
	autofitYscale=false;
	m_nShiftPixels = 1 ;
	m_nTrendPoints = 0 ;
	m_nMaxPointCnt=1024;
	CustShift.m_nPointsToDo=0;
	// G.Hayduk: actually I needed an OScopeCtrl to draw specific number of
	// data samples and stretch them on the plot ctrl. Now, OScopeCtrl has
	// two modes of operation: fixed Shift (when m_nTrendPoints=0, 
	// m_nShiftPixels is in use), or fixed number of Points in the plot width
	// (when m_nTrendPoints>0)
	// When m_nTrendPoints>0, CustShift structure is in use

	// background, grid and data colors
	// these are public variables and can be set directly
	m_crBackColor  = RGB(  0,   0,   0) ;  // see also SetBackgroundColor
	m_crGridColor  = RGB(  0, 255, 255) ;  // see also SetGridColor

	// protected variables
	m_brushBack=*(wxTheBrushList->FindOrCreateBrush(wxColour(GetRValue(m_crBackColor),GetGValue(m_crBackColor),GetBValue(m_crBackColor)),wxSOLID));
	  //*wxBLACK_BRUSH;//.CreateSolidBrush( m_crBackColor ) ;

	// public member variables, can be set directly 
	m_str.XUnits.Format("Samples") ;  // can also be set with SetXUnits
	m_str.YUnits.Format("Y units") ;  // can also be set with SetYUnits

	// protected bitmaps to restore the memory DC's
	m_pbitmapOldGrid = NULL ;
	m_pbitmapOldPlot = NULL ;

	// G.Hayduk: configurable number of grids init
	// you are free to change those between contructing the object 
	// and calling Create
	m_nXGrids = 6;
	m_nYGrids = 5;
	m_nTrendPoints = -1;

	m_bDoUpdate=true;
	m_nRedrawTimer.SetOwner(this);
}  // COScopeCtrl

/////////////////////////////////////////////////////////////////////////////
COScopeCtrl::~COScopeCtrl()
{
	// just to be picky restore the bitmaps for the two memory dc's
	// (these dc's are being destroyed so there shouldn't be any leaks)
	if (m_pbitmapOldGrid != NULL)
		m_dcGrid->SelectObject(wxNullBitmap) ;  
	if (m_pbitmapOldPlot != NULL)
		m_dcPlot->SelectObject(wxNullBitmap) ;  
	delete [] m_PlotData;

	delete m_dcGrid;
	delete m_dcPlot;
	if(m_bitmapPlot) delete m_bitmapPlot;
	if(m_bitmapGrid) delete m_bitmapGrid;

	// G.Hayduk: If anyone notices that I'm not freeing or deleting
	// something, please let me know: hayduk@hello.to
} // ~COScopeCtrl

#if 0
BEGIN_MESSAGE_MAP(COScopeCtrl, CWnd)
	//{{AFX_MSG_MAP(COScopeCtrl)
	ON_WM_PAINT()
	ON_WM_SIZE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif


/////////////////////////////////////////////////////////////////////////////
// COScopeCtrl message handlers

/////////////////////////////////////////////////////////////////////////////
#if 0
BOOL COScopeCtrl::Create(DWORD dwStyle, const RECT& rect, 
		                     CWnd* pParentWnd, UINT nID) 
{
	BOOL result ;
	static CString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW) ;

	result = CWnd::CreateEx(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE, 
		                      className, NULL, dwStyle, 
		                      rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,
		                      pParentWnd->GetSafeHwnd(), (HMENU)nID) ;
	if (result != 0)
		InvalidateCtrl() ;

	return result ;

} // Create
#endif

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetRange(double dLower, double dUpper, int iTrend)
{
	m_PlotData[ iTrend ].dLowerLimit     = dLower ;
	m_PlotData[ iTrend ].dUpperLimit     = dUpper ;
	m_PlotData[ iTrend ].dRange          = m_PlotData[ iTrend ].dUpperLimit - m_PlotData[ iTrend ].dLowerLimit ;
	m_PlotData[ iTrend ].dVerticalFactor = (double)m_nPlotHeight / m_PlotData[ iTrend ].dRange ; 

	// clear out the existing garbage, re-start with a clean plot
	InvalidateCtrl() ;

}  // SetRange

void COScopeCtrl::SetRanges(double dLower, double dUpper)
{
	int iTrend;
	
	for( iTrend = 0 ; iTrend<m_NTrends ; iTrend ++ ) {
		m_PlotData[ iTrend ].dLowerLimit     = dLower;
		m_PlotData[ iTrend ].dUpperLimit     = dUpper ;
		m_PlotData[ iTrend ].dRange          = m_PlotData[ iTrend ].dUpperLimit - m_PlotData[ iTrend ].dLowerLimit ;
		m_PlotData[ iTrend ].dVerticalFactor = (double)m_nPlotHeight / m_PlotData[ iTrend ].dRange ; 
	}

	// clear out the existing garbage, re-start with a clean plot
	InvalidateCtrl() ;

}  // SetRange
/////////////////////////////////////////////////////////////////////////////
// G.Hayduk: Apart from setting title of axis, now you can optionally set 
// the limits strings
// (string which will be placed on the left and right of axis)
void COScopeCtrl::SetXUnits(CString string, CString XMin, CString XMax )
{
	m_str.XUnits = string ;
	m_str.XMin = XMin;
	m_str.XMax = XMax;

	InvalidateCtrl(false) ;

}  // SetXUnits


/////////////////////////////////////////////////////////////////////////////
// G.Hayduk: Apart from setting title of axis, now you can optionally set 
// the limits strings
// (string which will be placed on the bottom and top of axis)
void COScopeCtrl::SetYUnits(CString string, CString YMin, CString YMax )
{
	m_str.YUnits = string ;
	m_str.YMin = YMin;
	m_str.YMax = YMax;

	// clear out the existing garbage, re-start with a clean plot
	InvalidateCtrl() ;

}  // SetYUnits

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetGridColor(COLORREF color)
{
	m_crGridColor = color ;

	// clear out the existing garbage, re-start with a clean plot
	InvalidateCtrl() ;

}  // SetGridColor


/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetPlotColor(COLORREF color, int iTrend)
{
	m_PlotData[ iTrend ].crPlotColor = color ;

	//m_PlotData[ iTrend ].penPlot.DeleteObject() ;
	m_PlotData[ iTrend ].penPlot=*(wxThePenList->FindOrCreatePen(wxColour(GetRValue(m_PlotData[iTrend].crPlotColor),GetGValue(m_PlotData[iTrend].crPlotColor),GetBValue(m_PlotData[iTrend].crPlotColor)),1,wxSOLID));
								     
	  //CreatePen(PS_SOLID, 0, m_PlotData[ iTrend ].crPlotColor) ;

	// clear out the existing garbage, re-start with a clean plot
	//	InvalidateCtrl() ;

}  // SetPlotColor

COLORREF COScopeCtrl::GetPlotColor( int iTrend )
{
	return m_PlotData[ iTrend ].crPlotColor;
}  // GetPlotColor

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetBackgroundColor(COLORREF color)
{
	m_crBackColor = color ;

	//m_brushBack.DeleteObject() ;
	//m_brushBack.CreateSolidBrush(m_crBackColor) ;
	m_brushBack=*(wxTheBrushList->FindOrCreateBrush(wxColour(GetRValue(m_crBackColor),GetGValue(m_crBackColor),GetBValue(m_crBackColor)),wxSOLID));

	// clear out the existing garbage, re-start with a clean plot
	InvalidateCtrl() ;

}  // SetBackgroundColor

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::InvalidateCtrl(bool deleteGraph)
{
	// There is a lot of drawing going on here - particularly in terms of 
	// drawing the grid.  Don't panic, this is all being drawn (only once)
	// to a bitmap.  The result is then BitBlt'd to the control whenever needed.
	int i, j, GridPos;
	int nCharacters ;

	// nothing you can do
	if(m_nClientWidth==0 || m_nClientHeight==0)
	  return;

	wxPen *oldPen ;
	//CPen solidPen(PS_SOLID, 0, m_crGridColor) ;
	wxPen solidPen=*(wxThePenList->FindOrCreatePen(wxColour(GetRValue(m_crGridColor),GetGValue(m_crGridColor),GetBValue(m_crGridColor)),1,wxSOLID));
	//wxFont axisFont, yUnitFont, *oldFont ;
	CString strTemp ;

	// in case we haven't established the memory dc's
	//CClientDC dc(this) ;  
	wxClientDC dc(this);

	// if we don't have one yet, set up a memory dc for the grid
	if (m_dcGrid == NULL)
	{
	  //m_dcGrid.CreateCompatibleDC(&dc) ;
	  m_dcGrid=new wxMemoryDC;
	  if(m_bitmapGrid) delete m_bitmapGrid;
	  m_bitmapGrid=NULL;
	  m_bitmapGrid=new wxBitmap(m_nClientWidth,m_nClientHeight);
	  m_dcGrid->SelectObject(*m_bitmapGrid);
	}
	
	//m_dcGrid.SetBkColor (m_crBackColor) ;

	// fill the grid background
	m_dcGrid->SetBrush(m_brushBack);
	m_dcGrid->SetPen(*wxTRANSPARENT_PEN);
	m_dcGrid->DrawRectangle(m_rectClient.left,m_rectClient.top,m_rectClient.right-m_rectClient.left,
				m_rectClient.bottom-m_rectClient.top);
	//m_dcGrid.FillRect(m_rectClient, &m_brushBack) ;

	// draw the plot rectangle:
	// determine how wide the y axis scaling values are
	nCharacters = abs((int)log10(fabs(m_PlotData[ 0 ].dUpperLimit))) ;
	nCharacters = max(nCharacters, abs((int)log10(fabs(m_PlotData[ 0 ].dLowerLimit)))) ;

	// add the units digit, decimal point and a minus sign, and an extra space
	// as well as the number of decimal places to display
	nCharacters = nCharacters + 4 + m_nYDecimals ;  

	// adjust the plot rectangle dimensions
	// assume 6 pixels per character (this may need to be adjusted)
	m_rectPlot.left = m_rectClient.left + 6*7+4;//(nCharacters) ;
	//m_rectPlot.x=m_rectClient.left+6*&;
	m_nPlotWidth    = m_rectPlot.right-m_rectPlot.left; //.Width() ;

	// draw the plot rectangle
	//oldPen = m_dcGrid.SelectObject (&solidPen) ; 
	m_dcGrid->SetPen(solidPen);
#if 0
	m_dcGrid.MoveTo (m_rectPlot.left, m_rectPlot.top) ;
	m_dcGrid.LineTo (m_rectPlot.right+1, m_rectPlot.top) ;
	m_dcGrid.LineTo (m_rectPlot.right+1, m_rectPlot.bottom+1) ;
	m_dcGrid.LineTo (m_rectPlot.left, m_rectPlot.bottom+1) ;
	m_dcGrid.LineTo (m_rectPlot.left, m_rectPlot.top) ;
	m_dcGrid.SelectObject (oldPen) ; 
#endif
	m_dcGrid->DrawLine(m_rectPlot.left,m_rectPlot.top,m_rectPlot.right+1,m_rectPlot.top);
	m_dcGrid->DrawLine(m_rectPlot.right+1,m_rectPlot.top,m_rectPlot.right+1,m_rectPlot.bottom+1);
	m_dcGrid->DrawLine(m_rectPlot.right+1,m_rectPlot.bottom+1,m_rectPlot.left,m_rectPlot.bottom+1);
	m_dcGrid->DrawLine(m_rectPlot.left,m_rectPlot.bottom+1,m_rectPlot.left,m_rectPlot.top);
	m_dcGrid->SetPen(wxNullPen);

	// draw the dotted lines, 
	// use SetPixel instead of a dotted pen - this allows for a 
	// finer dotted line and a more "technical" look
	// G.Hayduk: added configurable number of grids
	wxPen grPen(wxColour(GetRValue(m_crGridColor),GetGValue(m_crGridColor),GetBValue(m_crGridColor)),1,wxSOLID);
	m_dcGrid->SetPen(grPen);

	for( j=1 ; j<(m_nYGrids+1) ; j++ ){
		GridPos = (m_rectPlot.bottom-m_rectPlot.top)*j/( m_nYGrids + 1 ) + m_rectPlot.top ;
		for (i=m_rectPlot.left; i<m_rectPlot.right; i+=4)
		  //m_dcGrid.SetPixel ( i, GridPos, m_crGridColor ) ;
		  m_dcGrid->DrawPoint(i,GridPos);
	}

/*
	for( j=1 ; j<(m_nXGrids+1) ; j++ ){
		GridPos = m_rectPlot.Width()*j/( m_nXGrids + 1 ) + m_rectPlot.left ;
		for (i=m_rectPlot.top; i<m_rectPlot.bottom; i+=4)
			m_dcGrid.SetPixel ( GridPos, i, m_crGridColor ) ;
	}
*/

	// create some fonts (horizontal and vertical)
	// use a height of 14 pixels and 300 weight 
	// (these may need to be adjusted depending on the display)
#if 0
	axisFont.CreateFont (14, 0, 0, 0, 300,
		                   FALSE, FALSE, 0, ANSI_CHARSET,
		                   OUT_DEFAULT_PRECIS, 
		                   CLIP_DEFAULT_PRECIS,
		                   DEFAULT_QUALITY, 
		                   DEFAULT_PITCH|FF_SWISS, "Arial") ;
	yUnitFont.CreateFont (14, 0, 900, 0, 300,
		                   FALSE, FALSE, 0, ANSI_CHARSET,
		                   OUT_DEFAULT_PRECIS, 
		                   CLIP_DEFAULT_PRECIS,
		                   DEFAULT_QUALITY, 
		                   DEFAULT_PITCH|FF_SWISS, "Arial") ;
#endif
	wxFont *axisFont, *oldFont ;
	axisFont=new wxFont(8,wxSWISS,wxNORMAL,wxNORMAL,FALSE,"Arial");

	// grab the horizontal font
	//oldFont = m_dcGrid.SelectObject(&axisFont) ;
	m_dcGrid->SetFont(*axisFont);//,this);

	// y max
	//m_dcGrid.SetTextColor (m_crGridColor) ;
	m_dcGrid->SetTextForeground(wxColour(GetRValue(m_crGridColor),GetGValue(m_crGridColor),GetBValue(m_crGridColor)));
	//m_dcGrid.SetTextAlign (TA_RIGHT|TA_TOP) ;
	if( m_str.YMax.IsEmpty() )
		strTemp.Format ("%.*lf", m_nYDecimals, m_PlotData[ 0 ].dUpperLimit) ;
	else
		strTemp = m_str.YMax;
	//m_dcGrid.TextOut (m_rectPlot.left-4, m_rectPlot.top-7, strTemp) ;
	wxCoord sizX,sizY;

#ifdef __WXGTK20__
#if (wxMAJOR_VERSION==2) && (wxMINOR_VERSION==4) && (wxRELEASE_NUMBER==0)
	// well.. it doesn't work. so don't do it
#else
	m_dcGrid->GetTextExtent(strTemp,&sizX,&sizY);
	m_dcGrid->DrawText(strTemp,m_rectPlot.left-4-sizX,m_rectPlot.top-7);
#endif
#else
	m_dcGrid->GetTextExtent(strTemp,&sizX,&sizY);
	m_dcGrid->DrawText(strTemp,m_rectPlot.left-4-sizX,m_rectPlot.top-7);
#endif
	m_dcGrid->SetPen(wxNullPen);

	// y/2
	strTemp.Format ("%.*lf", m_nYDecimals, m_PlotData[ 0 ].dUpperLimit / 2) ;
	//m_dcGrid.TextOut (m_rectPlot.left-2, m_rectPlot.bottom+((m_rectPlot.top-m_rectPlot.bottom)/2)-7 , strTemp) ;
#ifdef __WXGTK20__
#if (wxMAJOR_VERSION==2) && (wxMINOR_VERSION==4) && (wxRELEASE_NUMBER==0)
	// it doesn't work. so don't do it.
#else
	m_dcGrid->GetTextExtent(strTemp,&sizX,&sizY);
	m_dcGrid->DrawText(strTemp,m_rectPlot.left-2-sizX,m_rectPlot.bottom+((m_rectPlot.top-m_rectPlot.bottom)/2)-7);
#endif
#else
	m_dcGrid->GetTextExtent(strTemp,&sizX,&sizY);
	m_dcGrid->DrawText(strTemp,m_rectPlot.left-2-sizX,m_rectPlot.bottom+((m_rectPlot.top-m_rectPlot.bottom)/2)-7);
#endif
	// y min
	//m_dcGrid.SetTextAlign (TA_RIGHT|TA_BASELINE) ;
	if( m_str.YMin.IsEmpty() )
		strTemp.Format ("%.*lf", m_nYDecimals, m_PlotData[ 0 ].dLowerLimit) ;
	else
		strTemp = m_str.YMin;
#ifdef __WXGTK20__
#if (wxMAJOR_VERSION==2) && (wxMINOR_VERSION==4) && (wxRELEASE_NUMBER==0)
	// well.. it doesn't work. so don't do it
#else
	m_dcGrid->GetTextExtent(strTemp,&sizX,&sizY);
	m_dcGrid->DrawText(strTemp,m_rectPlot.left-4-sizX, m_rectPlot.bottom);
	//m_dcGrid.TextOut (m_rectPlot.left-4-sizX, m_rectPlot.bottom, strTemp) ;
#endif
#else
	m_dcGrid->GetTextExtent(strTemp,&sizX,&sizY);
	m_dcGrid->DrawText(strTemp,m_rectPlot.left-4-sizX, m_rectPlot.bottom);
#endif
/*
	// x min
	m_dcGrid.SetTextAlign (TA_LEFT|TA_TOP) ;
	if( m_str.XMin.IsEmpty() )
		m_dcGrid.TextOut (m_rectPlot.left, m_rectPlot.bottom+4, "0") ;
	else
		m_dcGrid.TextOut( m_rectPlot.left, m_rectPlot.bottom+4, (LPCTSTR)m_str.XMin );

	// x max
	m_dcGrid.SetTextAlign (TA_RIGHT|TA_TOP) ;
	if( m_str.XMax.IsEmpty() ){
		if( m_nTrendPoints<0 )
			strTemp.Format ("%d", m_nPlotWidth/m_nShiftPixels) ; 
		else
			strTemp.Format ("%d", m_nTrendPoints-1 ) ; 
	} else
		strTemp = m_str.XMax;
	m_dcGrid.TextOut (m_rectPlot.right, m_rectPlot.bottom+4, strTemp) ;
*/
	// x units
	//m_dcGrid.SetTextAlign (TA_CENTER|TA_TOP) ;
#ifdef __WXGTK20__
#if (wxMAJOR_VERSION==2) && (wxMINOR_VERSION==4) && (wxRELEASE_NUMBER==0)
	// well.. it doesn't work. so don't do it
#else
	m_dcGrid->GetTextExtent(m_str.XUnits,&sizX,&sizY);
	//m_dcGrid.TextOut ((m_rectPlot.left+m_rectPlot.right)/2, 
	//	                m_rectPlot.bottom+4, m_str.XUnits) ;
	m_dcGrid->DrawText(m_str.XUnits,(m_rectPlot.left+m_rectPlot.right)/2-sizX/2,m_rectPlot.bottom+4);
#endif
#else
	m_dcGrid->GetTextExtent(m_str.XUnits,&sizX,&sizY);
	//m_dcGrid.TextOut ((m_rectPlot.left+m_rectPlot.right)/2, 
	//	                m_rectPlot.bottom+4, m_str.XUnits) ;
	if(!m_str.XUnits.IsEmpty()) 
	  m_dcGrid->DrawText(m_str.XUnits,(m_rectPlot.left+m_rectPlot.right)/2-sizX/2,m_rectPlot.bottom+4);
#endif
	// restore the font
	//m_dcGrid.SelectObject(oldFont) ;

	// y units
	//oldFont = m_dcGrid.SelectObject(&yUnitFont) ;
	//m_dcGrid.SetTextAlign (TA_CENTER|TA_BASELINE) ;
	//m_dcGrid.TextOut ((m_rectClient.left+m_rectPlot.left)/2, 
	//	                ((m_rectPlot.bottom+m_rectPlot.top)/2)-40, m_str.YUnits) ;
	m_dcGrid->GetTextExtent(m_str.YUnits,&sizX,&sizY);
	if(!m_str.YUnits.IsEmpty()) 
	  m_dcGrid->DrawRotatedText(m_str.YUnits,(m_rectClient.left+m_rectPlot.left+4)/2-(sizY*2),
				    ((m_rectPlot.bottom+m_rectPlot.top)/2)+sizX/2,90.0);
				  
	//m_dcGrid.SelectObject(oldFont) ;

	// at this point we are done filling the the grid bitmap, 
	// no more drawing to this bitmap is needed until the setting are changed
	
	// if we don't have one yet, set up a memory dc for the plot
	if (m_dcPlot == NULL)
	{  
	  // what on earth was going on in here??
	  m_dcPlot=new wxMemoryDC;
	  if(m_bitmapPlot) delete m_bitmapPlot;
	  m_bitmapPlot=NULL;
	  m_bitmapPlot=new wxBitmap(m_nClientWidth,m_nClientHeight);
	  m_dcPlot->SelectObject(*m_bitmapPlot);
	}

	// make sure the plot bitmap is cleared
	if (deleteGraph) {
	  //m_dcPlot.SetBkColor (m_crBackColor) ;
	  m_dcPlot->SetBrush(m_brushBack);
	  m_dcPlot->SetPen(*wxTRANSPARENT_PEN);
	  m_dcPlot->DrawRectangle(m_rectClient.left,m_rectClient.top,m_rectClient.right-m_rectClient.left,
				m_rectClient.bottom-m_rectClient.top);
	}

	if(true) {
	  int iNewSize=m_rectClient.right-m_rectClient.left;
	  if(m_nMaxPointCnt<iNewSize) 
	    m_nMaxPointCnt=iNewSize;

		m_bDoUpdate = false;
		
		//if(m_nRedrawTimer)
		//	KillTimer(m_nRedrawTimer);
		m_nRedrawTimer.Stop();
		//m_nRedrawTimer = SetTimer(1612, 100, NULL); // reduce flickering
		m_nRedrawTimer.SetOwner(this,TIMER_OSCOPE);
		m_nRedrawTimer.Start(100);
		//ReCreateGraph();
	}

	delete axisFont;

	// finally, force the plot area to redraw
	//InvalidateRect(m_rectClient) ;
	wxRect rrc;
	rrc.x=m_rectClient.left;
	rrc.y=m_rectClient.top;
	rrc.width=m_rectClient.right-m_rectClient.left;
	rrc.height=m_rectClient.bottom-m_rectClient.top;
	Refresh(FALSE,&rrc);
} // InvalidateCtrl


/////////////////////////////////////////////////////////////////////////////
// G.Hayduk: now, there are two methods: AppendPoints and AppendEmptyPoints
void COScopeCtrl::AppendPoints(double dNewPoint[], bool bInvalidate, bool bAdd2List )
{
	int iTrend;
	
	// append a data point to the plot
	for(iTrend = 0; iTrend < m_NTrends; iTrend ++)
	{
		m_PlotData[iTrend].dCurrentPosition = dNewPoint[iTrend];
		if(bAdd2List)
		{
			m_PlotData[iTrend].lstPoints.AddTail((float)dNewPoint[iTrend]);
			while(m_PlotData[iTrend].lstPoints.GetCount() > m_nMaxPointCnt)
				m_PlotData[iTrend].lstPoints.RemoveHead();
		}
	}
	
	if(m_nTrendPoints > 0)
	{
		if(CustShift.m_nPointsToDo == 0)
		{
			CustShift.m_nPointsToDo = m_nTrendPoints - 1;
			CustShift.m_nWidthToDo = m_nPlotWidth;
			CustShift.m_nRmndr = 0;
		}
		
		// a little bit tricky setting m_nShiftPixels in "fixed number of points through plot width" mode
		m_nShiftPixels = (CustShift.m_nWidthToDo + CustShift.m_nRmndr) / CustShift.m_nPointsToDo;
		CustShift.m_nRmndr = (CustShift.m_nWidthToDo + CustShift.m_nRmndr) % CustShift.m_nPointsToDo;
		if(CustShift.m_nPointsToDo == 1)
			m_nShiftPixels = CustShift.m_nWidthToDo;
		CustShift.m_nWidthToDo -= m_nShiftPixels;
		CustShift.m_nPointsToDo--;
	}
	DrawPoint();
	
	if(bInvalidate && m_bDoUpdate)
	  Refresh(FALSE);
	
	return;

} // AppendPoint

/////////////////////////////////////////////////////////////////////////////
// G.Hayduk:
// AppendEmptyPoints adds a vector of data points, without drawing them
// (but shifting the plot), this way you can do a "hole" (space) in the plot
// i.e. indicating "no data here". When points are available, call AppendEmptyPoints
// for first valid vector of data points, and then call AppendPoints again and again 
// for valid points

void COScopeCtrl::AppendEmptyPoints( double dNewPoint[],bool bInvalidate,bool bAdd2List )
{
  int iTrend, currY;
  RECT ScrollRect, rectCleanUp ;
  // append a data point to the plot
  // return the previous point
  for( iTrend = 0 ; iTrend<m_NTrends ; iTrend ++ ) {
    m_PlotData[ iTrend ].dCurrentPosition = dNewPoint[ iTrend ] ;
    if(bAdd2List)
      m_PlotData[iTrend].lstPoints.AddTail(dNewPoint[iTrend]);
  }
  if( m_nTrendPoints>0 ){
    if( CustShift.m_nPointsToDo==0 ){
      CustShift.m_nPointsToDo = m_nTrendPoints - 1 ;
      CustShift.m_nWidthToDo = m_nPlotWidth ;
      CustShift.m_nRmndr = 0;
    }
    m_nShiftPixels = (CustShift.m_nWidthToDo + CustShift.m_nRmndr ) / CustShift.m_nPointsToDo ;
    CustShift.m_nRmndr = (CustShift.m_nWidthToDo + CustShift.m_nRmndr ) % CustShift.m_nPointsToDo ;
    if( CustShift.m_nPointsToDo==1 )
      m_nShiftPixels = CustShift.m_nWidthToDo;
    CustShift.m_nWidthToDo -= m_nShiftPixels;
    CustShift.m_nPointsToDo--;
  }
  
// now comes DrawPoint's shift process

	if (m_dcPlot != NULL)
	{
		if( m_nShiftPixels>0 ){
			ScrollRect.left = m_rectPlot.left;
			ScrollRect.top  = m_rectPlot.top + 1;
			ScrollRect.right  = m_rectPlot.left + m_nPlotWidth;
			ScrollRect.bottom = m_rectPlot.top + 1 + m_nPlotHeight;
			ScrollRect = m_rectPlot;
			ScrollRect.right ++;
			// no scroll in wxWin
			//m_dcPlot.ScrollDC( -m_nShiftPixels, 0, (LPCRECT)&ScrollRect, (LPCRECT)&ScrollRect, NULL, NULL );
			m_dcPlot->Blit(m_rectPlot.left,m_rectPlot.top+1,m_nPlotWidth,m_nPlotHeight,m_dcPlot,
				       m_rectPlot.left+m_nShiftPixels,m_rectPlot.top+1);

			// establish a rectangle over the right side of plot
			// which now needs to be cleaned up proir to adding the new point
			rectCleanUp = m_rectPlot ;
			rectCleanUp.left  = rectCleanUp.right - m_nShiftPixels + 1;
			rectCleanUp.right ++ ;
			// fill the cleanup area with the background
			//m_dcPlot.FillRect(rectCleanUp, &m_brushBack) ;
			m_dcPlot->SetBrush(m_brushBack);
			m_dcPlot->SetPen(*wxTRANSPARENT_PEN);
			m_dcPlot->DrawRectangle(rectCleanUp.left,rectCleanUp.top,rectCleanUp.right-rectCleanUp.left,
						rectCleanUp.bottom-rectCleanUp.top);
		}

		// draw the next line segement
		for( iTrend = 0 ; iTrend<m_NTrends ; iTrend ++ ){
			currY = m_rectPlot.bottom -
					(long)((m_PlotData[ iTrend ].dCurrentPosition - m_PlotData[ iTrend ].dLowerLimit) * m_PlotData[ iTrend ].dVerticalFactor) ;
			m_PlotData[ iTrend ].nPrevY = currY;

			// store the current point for connection to the next point
			m_PlotData[ iTrend ].dPreviousPosition = m_PlotData[ iTrend ].dCurrentPosition ;
		}
	}

// -----------------------------------------

	if(bInvalidate && m_bDoUpdate)
	  Refresh(FALSE);

	//Invalidate() ;
	return;

} // AppendEmptyPoint
 
////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::OnPaint(wxPaintEvent& evt) 
{
	wxPaintDC dc(this) ;  // device context for painting
	wxMemoryDC memDC ;
	wxBitmap* memBitmap ;
	wxBitmap* oldBitmap ; // bitmap originally found in CMemDC

	// no real plotting work is performed here, 
	// just putting the existing bitmaps on the client

	// to avoid flicker, establish a memory dc, draw to it 
	// and then BitBlt it to the client

	//memDC.CreateCompatibleDC(&dc) ;
	//memBitmap.CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight) ;
	memBitmap=new wxBitmap(m_nClientWidth,m_nClientHeight);
	//oldBitmap = (CBitmap *)memDC.SelectObject(&memBitmap) ;
	memDC.SelectObject(*memBitmap);

	if (memDC.Ok())
	{
		// first drop the grid on the memory dc
		//memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight, 
		//             &m_dcGrid, 0, 0, SRCCOPY) ;
	  memDC.Blit(0,0,m_nClientWidth,m_nClientHeight,m_dcGrid,0,0);
		// now add the plot on top as a "pattern" via SRCPAINT.
		// works well with dark background and a light plot
		//memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight, 
	  //            &m_dcPlot, 0, 0, SRCPAINT) ;  //SRCPAINT
	  memDC.Blit(0,0,m_nClientWidth,m_nClientHeight,m_dcPlot,0,0,wxOR);
		// finally send the result to the display
		//dc.BitBlt(0, 0, m_nClientWidth, m_nClientHeight, 
	  //        &memDC, 0, 0, SRCCOPY) ;
	  dc.Blit(0,0,m_nClientWidth,m_nClientHeight,&memDC,0,0);
	}

	//memDC.SelectObject(oldBitmap) ;
	memDC.SelectObject(wxNullBitmap);
	// free it. please. please. please.
	delete memBitmap;
} // OnPaint

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::DrawPoint()
{
	// this does the work of "scrolling" the plot to the left
	// and appending a new data point all of the plotting is 
	// directed to the memory based bitmap associated with m_dcPlot
	// the will subsequently be BitBlt'd to the client in OnPaint
	
	int currX, prevX, currY, prevY, iTrend ;

	//CPen *oldPen ;
	RECT ScrollRect, rectCleanUp ;

	if (m_dcPlot != NULL)
	{
//	BitBlt was replaced by call to ScrollDC
//		m_dcPlot.BitBlt(m_rectPlot.left, m_rectPlot.top+1, 
//		                m_nPlotWidth, m_nPlotHeight, &m_dcPlot, 
//		                m_rectPlot.left+m_nShiftPixels, m_rectPlot.top+1, 
//		                SRCCOPY) ;
		if( m_nShiftPixels>0 ){
			ScrollRect.left = m_rectPlot.left;
			ScrollRect.top  = m_rectPlot.top + 1;
			ScrollRect.right  = m_rectPlot.left + m_nPlotWidth;
			ScrollRect.bottom = m_rectPlot.top + 1 + m_nPlotHeight;
			ScrollRect = m_rectPlot;
			ScrollRect.right ++;
			// no scroll in wxWIn
			//m_dcPlot.ScrollDC( -m_nShiftPixels, 0, (LPCRECT)&ScrollRect, (LPCRECT)&ScrollRect, NULL, NULL );
			m_dcPlot->Blit(m_rectPlot.left,m_rectPlot.top+1,m_nPlotWidth,m_nPlotHeight,m_dcPlot,
				       m_rectPlot.left+m_nShiftPixels,m_rectPlot.top+1);
			//printf("TODO: Scroll\n");

			// establish a rectangle over the right side of plot
			// which now needs to be cleaned up proir to adding the new point
			rectCleanUp = m_rectPlot ;
			rectCleanUp.left  = rectCleanUp.right - m_nShiftPixels;// + 1;
			rectCleanUp.right ++ ;
			// fill the cleanup area with the background
			m_dcPlot->SetPen(*wxTRANSPARENT_PEN);
			m_dcPlot->SetBrush(m_brushBack);
			m_dcPlot->DrawRectangle(rectCleanUp.left,rectCleanUp.top,rectCleanUp.right-rectCleanUp.left,
						rectCleanUp.bottom-rectCleanUp.top);
			//m_dcPlot.FillRect(rectCleanUp, &m_brushBack) ;
		}

		// draw the next line segement
		for( iTrend = 0 ; iTrend<m_NTrends ; iTrend ++ ){
			// grab the plotting pen
			//oldPen = m_dcPlot.SelectObject(&m_PlotData[ iTrend ].penPlot) ;
		  m_dcPlot->SetPen(m_PlotData[iTrend].penPlot);
		  
		  // move to the previous point
		  prevX = m_rectPlot.right-m_nShiftPixels ;
		  if( m_PlotData[ iTrend ].nPrevY > 0 ){
		    prevY = m_PlotData[ iTrend ].nPrevY;
		  } else
		    prevY = m_rectPlot.bottom - 
		      (long)((m_PlotData[ iTrend ].dPreviousPosition - m_PlotData[ iTrend ].dLowerLimit) * m_PlotData[ iTrend ].dVerticalFactor) ;
		  wxCoord sX,sY;
		  sX=prevX-1; sY=prevY;
		  //m_dcPlot.MoveTo (prevX-1, prevY) ;
		  
		  // draw to the current point
		  currX = m_rectPlot.right ;
		  currY = m_rectPlot.bottom -
		    (long)((m_PlotData[ iTrend ].dCurrentPosition - m_PlotData[ iTrend ].dLowerLimit) * m_PlotData[ iTrend ].dVerticalFactor) ;
		  m_PlotData[ iTrend ].nPrevY = currY;
		  if( abs(prevX-currX)>abs(prevY-currY) ){
		    currX += prevX-currX>0 ? -1 : 1 ;
		  } else {
		    currY += prevY-currY>0 ? -1 : 1 ;
		  }
		  //m_dcPlot.LineTo (currX-1, currY) ;
		  m_dcPlot->DrawLine(sX,sY,currX-1,currY);

		  //if (drawBars) m_dcPlot.LineTo(currX-1,m_rectPlot.bottom);
		  if(drawBars) m_dcPlot->DrawLine(currX-1,currY,currX-1,m_rectPlot.bottom);
		  //m_dcPlot.Rectangle(currX-1,currY,currX-1,m_rectPlot.bottom);
		  
		  // restore the pen 
		  //m_dcPlot.SelectObject(oldPen) ;
		  
		  // if the data leaks over the upper or lower plot boundaries
		  // fill the upper and lower leakage with the background
		  // this will facilitate clipping on an as needed basis
		  // as opposed to always calling IntersectClipRect
		  if ((prevY <= m_rectPlot.top) || (currY <= m_rectPlot.top)) {
		    //m_dcPlot.FillRect(CRect(prevX-1, m_rectClient.top, currX+5, m_rectPlot.top+1), &m_brushBack) ;
		    m_dcPlot->SetBrush(m_brushBack);
		    m_dcPlot->SetPen(*wxTRANSPARENT_PEN);
		    m_dcPlot->DrawRectangle(prevX-1,m_rectClient.top,(currX+5)-(prevX-1),(m_rectPlot.top+1)-(m_rectClient.top));
		  }
		  if ((prevY >= m_rectPlot.bottom) || (currY >= m_rectPlot.bottom)) {
		    //m_dcPlot.FillRect(CRect(prevX-1, m_rectPlot.bottom+1, currX+5, m_rectClient.bottom+1), &m_brushBack) ;
		    m_dcPlot->SetBrush(m_brushBack);
		    m_dcPlot->SetPen(*wxTRANSPARENT_PEN);
		    m_dcPlot->DrawRectangle(prevX-1,m_rectPlot.bottom+1,(currX+5)-(prevX-1),(m_rectClient.bottom+1)-(m_rectPlot.bottom+1));
		  }
		  
		  // store the current point for connection to the next point
		  m_PlotData[ iTrend ].dPreviousPosition = m_PlotData[ iTrend ].dCurrentPosition ;
		}
	}
} // end DrawPoint

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::OnSize(wxSizeEvent& evt) //UINT nType, int cx, int cy)
{

	int iTrend;
	//CWnd::OnSize(nType, cx, cy) ;
	
#if 0
	if(!theApp.emuledlg)
	  return;

	if(!theApp.emuledlg->IsRunning())
	  return;
#endif

	if(oldwidth==evt.GetSize().GetWidth() && oldheight==evt.GetSize().GetHeight()) {
	  // same size. don't touch
	  return;
	}
	oldwidth=evt.GetSize().GetWidth();
	oldheight=evt.GetSize().GetHeight();

	// NOTE: OnSize automatically gets called during the setup of the control

	wxRect myrect;
	myrect=GetClientRect();
	//GetClientRect(m_rectClient) ;
	m_rectClient.left=myrect.x;
	m_rectClient.top=myrect.y;
	m_rectClient.right=myrect.x+myrect.width;
	m_rectClient.bottom=myrect.y+myrect.height;

	// 
	if(myrect.width<1 || myrect.height<1)
	  return;

	// set some member variables to avoid multiple function calls
	m_nClientHeight = myrect.height;//m_rectClient.Height() ;
	m_nClientWidth  = myrect.width;//m_rectClient.Width() ;

	// the "left" coordinate and "width" will be modified in
	// InvalidateCtrl to be based on the width of the y axis scaling
	m_rectPlot.left   = 20 ; 
	m_rectPlot.top    = 10 ;
	m_rectPlot.right  = m_rectClient.right-10 ;
	m_rectPlot.bottom = m_rectClient.bottom-25 ;

	// set some member variables to avoid multiple function calls
	m_nPlotHeight = m_rectPlot.bottom-m_rectPlot.top;//Height() ;
	m_nPlotWidth  = m_rectPlot.right-m_rectPlot.left;//Width() ;

	// set the scaling factor for now, this can be adjusted
	// in the SetRange functions
	for( iTrend=0 ; iTrend<m_NTrends ; iTrend ++ ) {
	  m_PlotData[ iTrend ].dVerticalFactor = (double)m_nPlotHeight / m_PlotData[ iTrend ].dRange ;
	}

	// destroy grid dc and bitmap (Invalidate recreates it)
	if(m_dcGrid) {
	  m_dcGrid->SelectObject(wxNullBitmap);
	  if(m_bitmapGrid) delete m_bitmapGrid;
	  delete m_dcGrid;
	  m_bitmapGrid=NULL;
	  m_dcGrid=NULL;
	}
	if(m_dcPlot) {
	  m_dcPlot->SelectObject(wxNullBitmap);
	  if(m_bitmapPlot) delete m_bitmapPlot;
	  delete m_dcPlot;
	  m_bitmapPlot=NULL;
	  m_dcPlot=NULL;
	}
#if 0
	// destroy and recreate the grid bitmap
	CClientDC dc(this) ; 
	if (m_pbitmapOldGrid && m_bitmapGrid.GetSafeHandle() && m_dcGrid.GetSafeHdc())
	{
		m_dcGrid.SelectObject(m_pbitmapOldGrid);
		m_bitmapGrid.DeleteObject();
		m_bitmapGrid.CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight) ;
		m_pbitmapOldGrid = m_dcGrid.SelectObject(&m_bitmapGrid) ;
	}

	// destroy and recreate the plot bitmap
	if (m_pbitmapOldPlot && m_bitmapPlot.GetSafeHandle() && m_dcPlot.GetSafeHdc())
	{
		m_dcPlot.SelectObject(m_pbitmapOldPlot);
		m_bitmapPlot.DeleteObject();
		m_bitmapPlot.CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight) ;
		m_pbitmapOldPlot = m_dcPlot.SelectObject(&m_bitmapPlot) ;
	}

	if(m_nPlotWidth>0 && m_nPlotHeight>0)
	  InvalidateCtrl();
#endif
	if(m_nPlotWidth>0 && m_nPlotHeight>0)
	  InvalidateCtrl();
} // OnSize


/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::Reset()
{
	// to clear the existing data (in the form of a bitmap)
	// simply invalidate the entire control
  InvalidateCtrl() ;
}

int COScopeCtrl::ReCreateGraph(void)
{
        int i;
        for(i = 0; i < m_NTrends; i++)
        {
                m_PlotData[i].dPreviousPosition = 0.0;
                m_PlotData[i].nPrevY = -1;
        }

	double *pAddPoints = new double[m_NTrends];
	
	int iCnt = m_PlotData[0].lstPoints.GetCount();
	for(int i = 0; i < iCnt; i++)
	{	
		for(int iTrend = 0; iTrend < m_NTrends; iTrend++)
		{
#if 0
		  m_PlotData[iTrend].nPrevY=-1;
		  m_PlotData[iTrend].dPreviousPosition=0.0;
#endif

			POSITION pos = m_PlotData[iTrend].lstPoints.FindIndex(i);
			if(pos)
				pAddPoints[iTrend] = m_PlotData[iTrend].lstPoints.GetAt(pos);
			else
				pAddPoints[iTrend] = 0;
		}
		AppendPoints(pAddPoints, false, false);
	}
	
	delete[] pAddPoints;

	return 0;
}

//void COScopeCtrl::OnTimer(UINT nIDEvent)
void COScopeCtrl::OnTimer(wxTimerEvent& evt)
{
  if(!theApp.emuledlg->IsRunning())
    return;

  m_nRedrawTimer.Stop();
  m_bDoUpdate=true;
  ReCreateGraph();
#if 0
	if(nIDEvent == m_nRedrawTimer)
	{
		KillTimer(m_nRedrawTimer);
		m_nRedrawTimer = 0;
		m_bDoUpdate = true;
		ReCreateGraph();
	}

	CWnd::OnTimer(nIDEvent);
#endif
}
